/*
 * Copyright 2010 - Jeremie Brebec
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.jb.sharedbills.shared.model.notification;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

//@Embeddable
public class ObservableList<T extends Notifier> implements List<T>, ObservableCollection<T>, NotificationListener {

	/**
	 * 
	 */
	private static final long serialVersionUID = 3042819716235121000L;
	
	public ObservableList() {
		internal = new ArrayList<T>();
	}
	
	/**
	 * 
	 */
	public ObservableList( Notifier owner, String property ) {
		internal = new ArrayList<T>();
		listeners = null;
		this.property = property;
		this.owner = owner;
	}
	
	/*
	 * (non-Javadoc)
	 * @see java.util.List#add(java.lang.Object)
	 */
	@Override
	public boolean add(T e) {
		if (list().add(e)) {
			
			doNotify( register( Collections.singleton(e) ), null );
			
			
			return true;
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#add(int, java.lang.Object)
	 */
	@Override
	public void add(int index, T element) {
		list().add(index,element);
		doNotify( register( Collections.singleton(element) ), null );
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#addAll(java.util.Collection)
	 */
	@Override
	public boolean addAll(Collection<? extends T> c) {
		if (list().addAll(c)) {
			
			doNotify( register(c), null );
			return true;
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#addAll(int, java.util.Collection)
	 */
	@Override
	public boolean addAll(int index, Collection<? extends T> c) {
		if (list().addAll(index,c)) {
			doNotify( register(c), null );
			return true;
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#clear()
	 */
	@Override
	public void clear() {
		unregister(this);
		
		Collection<T> removedObjects = null;
		if (listeners != null && !listeners.isEmpty()) {
			removedObjects = new ArrayList<T>( this );
		}
		list().clear();
		
		CollectionNotification<T> notification = new CollectionNotification<T>( owner, property, Collections.<T>emptyList(), removedObjects );
		for( NotificationListener listener : listeners ) {
			listener.handle(notification);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#contains(java.lang.Object)
	 */
	@Override
	public boolean contains(Object o) {
		return list().contains(o);
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#containsAll(java.util.Collection)
	 */
	@Override
	public boolean containsAll(Collection<?> c) {
		return list().containsAll(c);
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#get(int)
	 */
	@Override
	public T get(int index) {
		return list().get(index);
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#indexOf(java.lang.Object)
	 */
	@Override
	public int indexOf(Object o) {
		return list().indexOf(o);
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#isEmpty()
	 */
	@Override
	public boolean isEmpty() {
		return list().isEmpty();
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#iterator()
	 */
	@Override
	public Iterator<T> iterator() {
		return list().iterator();
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#lastIndexOf(java.lang.Object)
	 */
	@Override
	public int lastIndexOf(Object o) {
		return list().lastIndexOf(o);
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#listIterator()
	 */
	@Override
	public ListIterator<T> listIterator() {
		return list().listIterator();
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#listIterator(int)
	 */
	@Override
	public ListIterator<T> listIterator(int index) {
		return list().listIterator(index);
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#remove(java.lang.Object)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean remove(Object o) {
		if (list().remove(o)) {
			
			doNotify( null, unregister(Collections.singletonList((T)o)) );
			return true;
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#remove(int)
	 */
	@Override
	public T remove(int index) {
		T result = list().remove(index);
		if (result != null) {
			doNotify( null, unregister(Collections.singletonList(result)));
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#removeAll(java.util.Collection)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean removeAll(Collection<?> c) {
		if( list().removeAll(c) ) {
			
			doNotify(null, (Collection<T>)c); // TODO: Filter..
			
			return true;
		}
		return false;
		
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#retainAll(java.util.Collection)
	 */
	@Override
	public boolean retainAll(Collection<?> c) {
		return list().retainAll(c);
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#set(int, java.lang.Object)
	 */
	@Override
	public T set(int index, T element) {
		return list().set(index, element);
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#size()
	 */
	@Override
	public int size() {
		return list().size();
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#subList(int, int)
	 */
	@Override
	public List<T> subList(int fromIndex, int toIndex) {
		return list().subList(fromIndex, toIndex);
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#toArray()
	 */
	@Override
	public Object[] toArray() {
		return list().toArray();
	}

	/*
	 * (non-Javadoc)
	 * @see java.util.List#toArray(T[])
	 */
	@Override
	public <O> O[] toArray(O[] a) {
		return list().toArray(a);
	}

	/*
	 * (non-Javadoc)
	 * @see org.jb.sharedbills.shared.model.notification.Notifier#addListener(org.jb.sharedbills.shared.model.notification.Notification)
	 */
	@Override
	public void addListener(NotificationListener listener) {
		if (listeners == null) {
			listeners = new ArrayList<NotificationListener>();
		}
		listeners.add( listener );
		needRegistration();
	}

	/*
	 * (non-Javadoc)
	 * @see org.jb.sharedbills.shared.model.notification.Notifier#removeListener(org.jb.sharedbills.shared.model.notification.NotificationListener)
	 */
	@Override
	public void removeListener(NotificationListener listener) {
		if (listeners == null)
			return;
		
		listeners.remove(listener);	
	}

	/**
	 * 
	 * @return
	 */
	final private List<T> list() {
		return internal;
	}
	
	/**
	 * 
	 * @param elements
	 */
	private Collection<? extends T> register( Collection<? extends T> elements ) {
		
		if (registrationNeeded) {
			for( T element : elements ) {
				if (element instanceof Notifier) {
					((Notifier) element).addListener(this);
				}
			}
		}
		
		return elements;
	}
	
	/**
	 * 
	 * @param elements
	 */
	private Collection<? extends T> unregister( Collection<? extends T> elements ) {
		
		if (registrationNeeded) {
			for( T element : elements ) {
				if (element instanceof Notifier) {
					((Notifier) element).removeListener(this);
				}
			}
		}
		
		return elements;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.jb.sharedbills.shared.model.notification.NotificationListener#handle(org.jb.sharedbills.shared.model.notification.Notification)
	 */
	@Override
	public void handle(Notification notification) {
		if (listeners == null || listeners.size() == 0) 
			return;
		
		for( NotificationListener listener : listeners ) {
			listener.handle(notification);
		}
	}
	
	/**
	 * 
	 * @param added
	 * @param removed
	 */
	final private void doNotify( Collection<? extends T> added, Collection<? extends T> removed ) {
		if (listeners == null || listeners.size() == 0)
			return;
		
		if (added == null)
			added = Collections.emptyList();
		
		if (removed == null)
			removed = Collections.emptyList();
		
		CollectionNotification<T> notification = new CollectionNotification<T>( owner, property, added, removed );
		for( NotificationListener listener : listeners ) {
			listener.handle(notification);
		}
	}
	
	/**
	 * 
	 */
	final private void needRegistration() {
		if (registrationNeeded)
			return;
		
		registrationNeeded = true;
		register(this);
	}
	
	private ArrayList<T> internal;
	private Notifier owner = null;
	private String property = null;
	private transient ArrayList<NotificationListener> listeners;
	private transient boolean registrationNeeded = false;
	
}
